using System.Drawing;
using System.Drawing.Imaging;
using Microsoft.Extensions.Logging;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using _3Dto2D.Configuration;
using _3Dto2D.Interfaces;
using _3Dto2D.Models;
using Color = System.Drawing.Color;

namespace _3Dto2D.Services;

public class ModernRenderEngine : IRenderEngine
{
    private readonly ILogger<ModernRenderEngine> _logger;
    private readonly RenderSettings _settings;
    private Shader? _shader;
    private Shader? _shader2d;
    private TextureRenderer? _textureRenderer;
    private readonly Dictionary<string, ModelRenderer> _modelCache = new();
    private bool _disposed;

    public bool IsInitialized => _shader != null;

    public event EventHandler<RenderErrorEventArgs>? RenderError;

    public ModernRenderEngine(ILogger<ModernRenderEngine> logger, RenderSettings settings)
    {
        _logger = logger;
        _settings = settings;
    }

    public void Initialize()
    {
        try
        {
            _shader = new Shader("Shaders/3d.vert", "Shaders/3d.frag");
            _shader2d = new Shader("Shaders/shader_2d.vert", "Shaders/shader_2d.frag");
            _textureRenderer = new TextureRenderer();
            _logger.LogInformation("Render engine initialized.");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to initialize render engine");
            OnRenderError(ex, "Failed to initialize render engine");
        }
    }

    public void RenderFrame(FrameData? frameData)
    {
        if (!IsInitialized || _shader == null)
        {
            return;
        }

        try
        {
            GL.Viewport(0, 0, _settings.OutputWidth, _settings.OutputHeight);
            GL.ClearColor(_settings.BackgroundColor);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            // Render 2D image from .lib
            if (frameData?.Image != null && _shader2d != null && _textureRenderer != null)
            {
                GL.Disable(EnableCap.DepthTest); // Disable depth testing for 2D overlay

                _shader2d.Use();
                _textureRenderer.LoadTexture(frameData.Image);

                var projection = Matrix4.CreateOrthographicOffCenter(0, _settings.OutputWidth, _settings.OutputHeight, 0, -1f, 1f);

                var modelMatrix = Matrix4.CreateScale(frameData.Image.Width, frameData.Image.Height, 1.0f) *
                                  Matrix4.CreateTranslation(
                                    _settings.CharacterGlobalOffsetX + frameData.OffsetX + frameData.Image.Width / 2f,
                                    _settings.CharacterGlobalOffsetY + frameData.OffsetY + frameData.Image.Height / 2f,
                                    0);


                _shader2d.SetUniform("uModel", modelMatrix);
                _shader2d.SetUniform("uProjection", projection);

                _textureRenderer.Render();

                GL.Enable(EnableCap.DepthTest); // Re-enable for next frame's 3D render
            }

            // Render 3D model
            if (frameData?.Mesh != null && !string.IsNullOrEmpty(frameData.Mesh.FilePath) && File.Exists(frameData.Mesh.FilePath))
            {
                if (!_modelCache.TryGetValue(frameData.Mesh.FilePath, out var model))
                {
                    model = new ModelRenderer(frameData.Mesh.FilePath);
                    _modelCache[frameData.Mesh.FilePath] = model;
                }

                _shader.Use();

                // Use the same projection as the 2D shader to align coordinate systems
                var projection = Matrix4.CreateOrthographicOffCenter(0, _settings.OutputWidth, _settings.OutputHeight, 0, -1000f, 1000f);

                var transform = frameData.Transform;
                // Match the original engine's rotation order (Y -> X -> Z)
                var modelMatrix = Matrix4.CreateRotationY(MathHelper.DegreesToRadians(-transform.RotationY)) * // Invert Y for coordinate system difference
                                  Matrix4.CreateRotationX(MathHelper.DegreesToRadians(transform.RotationX)) *
                                  Matrix4.CreateRotationZ(MathHelper.DegreesToRadians(transform.RotationZ));

                // The 3D model's position must be relative to the 2D character sprite.
                // We combine the global character offset, the frame's specific offset, and the transform's own move values.
                var totalOffsetX = _settings.CharacterGlobalOffsetX + frameData.OffsetX + transform.MoveX;
                var totalOffsetY = _settings.CharacterGlobalOffsetY + frameData.OffsetY + transform.MoveY;

                var viewMatrix = Matrix4.CreateTranslation(totalOffsetX, totalOffsetY, 0);

                _shader.SetUniform("model", modelMatrix);
                _shader.SetUniform("view", viewMatrix);
                _shader.SetUniform("projection", projection);

                model.Render(_shader);
            }


        }
        catch (Exception ex)
        {
            if(frameData?.Mesh?.FilePath != null)
                _logger.LogError(ex, "Failed to render mesh {FilePath}", frameData.Mesh.FilePath);
            else
                _logger.LogError(ex, "Failed to render mesh");
            OnRenderError(ex, "Failed to render mesh");
        }
    }

    public Bitmap? RenderFrameToBitmap(FrameData frameData)
    {
        RenderFrame(frameData);

        try
        {
            var pixels = new byte[_settings.OutputWidth * _settings.OutputHeight * 4];
            GL.ReadPixels(0, 0, _settings.OutputWidth, _settings.OutputHeight, OpenTK.Graphics.OpenGL4.PixelFormat.Rgba, PixelType.UnsignedByte, pixels);
            return CreateBitmapFromPixelData(pixels, _settings.OutputWidth, _settings.OutputHeight);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to render frame to bitmap");
            OnRenderError(ex, "Failed to render frame to bitmap");
            return null;
        }
    }

    private static Bitmap CreateBitmapFromPixelData(byte[] pixelData, int width, int height)
    {
        var bitmap = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
        var bitmapData = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, bitmap.PixelFormat);

        for (int y = 0; y < height; y++)
        {
            var sourceIndex = (height - 1 - y) * width * 4;
            var destPtr = IntPtr.Add(bitmapData.Scan0, y * bitmapData.Stride);
            System.Runtime.InteropServices.Marshal.Copy(pixelData, sourceIndex, destPtr, width * 4);
        }

        bitmap.UnlockBits(bitmapData);
        return bitmap;
    }

    public void SetBackgroundColor(Color color)
    {
        _settings.BackgroundColor = color;
    }

    public void SetViewport(int width, int height)
    {
        _settings.OutputWidth = width;
        _settings.OutputHeight = height;
    }

    private void OnRenderError(Exception exception, string message)
    {
        RenderError?.Invoke(this, new RenderErrorEventArgs(exception, message));
    }

    public void Dispose()
    {
        if (_disposed) return;

        _logger.LogInformation("Disposing render engine");

        _shader?.Dispose();
        _shader2d?.Dispose();
        _textureRenderer?.Dispose();
        foreach (var model in _modelCache.Values)
        {
            model.Dispose();
        }
        _modelCache.Clear();

        _disposed = true;
    }

}
